/**
 * MessagesMap.java
 *
 * Copyright 2010 Jack Leow
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package my.edu.clhs.springframework.web.servlet.view.stringtemplate3.support;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.context.NoSuchMessageException;
import org.springframework.web.servlet.support.RequestContext;

/**
 * Class that allows ResourceBundle messages to be accessed via a Map interface
 * so as to be accessible as part of the StringTemplate model.
 *
 * This map is immutable. It also does not allow iteration over keys/values.
 * More specifically, only the {@link #containsKey(Object)} and
 * {@link #get(Object)} methods are implemented. All other {@link java.util.Map}
 * methods throw <tt>UnsupportedOperationException</tt>.
 *
 * @author Jack Leow
 * @version 1.0
 * @since 1.0 (Dec 23, 2010, 2:43 PM)
 */
public class MessagesMap extends AbstractMap {
    private final RequestContext requestContext;
    
    public MessagesMap(RequestContext requestContext) {
        this.requestContext = requestContext;
    }
    
    // TODO document (this is going to be fun)
    private class MessageMap extends AbstractMap {
        private final MessageMap parent;
        private final Object key;
        
        MessageMap(Object key) {
            this.parent = null;
            this.key = key;
        }
        
        private MessageMap(MessageMap parent, Object key) {
            this.parent = parent;
            this.key = key;
        }
        
        /**
         * StringTemplate renders maps by iterating through its values and
         * rendering them one by one. Modify {@link Map#entrySet()} behavior so
         * that messages gets rendered correctly.
         */
        // @Override
        public Set entrySet() {
            return Collections.singleton(
                new Map.Entry() {
                    public Object setValue(Object arg0) {
                        throw new UnsupportedOperationException(
                            "not implemented");
                    }
                    
                    public Object getValue() {
                        return MessageMap.this.toString();
                    }
                    
                    public Object getKey() {
                        return null;
                    }
                }
            );
        }
        
        // TODO document and cache
        /**
         * Always returns <tt>true</tt>.
         */
        // @Override
        public boolean containsKey(Object key) {
            return true;
        }
        
        // @Override
        public Object get(Object key) {
            return new MessageMap(this, key);
        }
        
        private String toString(List args) {
            String str;
            
            if (parent != null) {
                args.add(0, key);
                
                str = parent.toString(args);
            } else {
                str = requestContext.getMessage(
                    key.toString(), args.toArray(), "???" + key + "???", false);
            }
            
            return str;
        }
        
        private String string;
        
        // @Override
        public String toString() {
            if (string == null) string = toString(new ArrayList());
            
            return string;
        }
    }
    
    /**
     * This implementation always throws <tt>UnsupportedOperationException</tt>.
     */
    // @Override
    public Set entrySet() {
        throw new UnsupportedOperationException("not implemented");
    }
    
    /**
     * Returns <tt>true</tt> if message for the specified key exists in the
     * underlying {@link RequestContext}.
     */
    // @Override
    public boolean containsKey(Object key) {
        try {
            requestContext.getMessage(key.toString());
            
            return true;
        } catch (NoSuchMessageException e) {
            return false;
        }
    }
    
    /**
     * Returns a {@link MessageMap} for the message for the specified key.
     * 
     * <p>This will always return a <tt>MessageMap</tt> even if no message
     * exists for the specified key. This is so that parameters can be passed
     * in without resulting in runtime exceptions. We handle undefined keys
     * while rendering the message.
     */
    // @Override
    public Object get(Object key) {
        return new MessageMap(key);
    }
}